/*----------------------------------------------------------------------------
 * Copyright (c) Huawei Technologies Co., Ltd. 2012-2019. All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 * conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 * of conditions and the following disclaimer in the documentation and/or other materials
 * provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 * to endorse or promote products derived from this software without specific prior written
 * permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *---------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------
 * Notice of Export Control Law
 * ===============================================
 * Huawei LiteOS may be subject to applicable export control laws and regulations, which might
 * include those applicable to Huawei LiteOS of U.S. and the country in which you are located.
 * Import, export and usage of Huawei LiteOS in any manner by you shall be in compliance with such
 * applicable export control laws and regulations.
 *---------------------------------------------------------------------------*/
 /*
  * Description: create proc info for sdhci host control
  * Author: wenhao w00470829
  * Create: 2019-1-24
  * History: 2018-01-01 wenhao w00470829 create
  */
#include "linux/seq_file.h"
#include "proc_fs.h"
#include "sys/statfs.h"
#include "sys/mount.h"
#include "stdio.h"
#include "disk.h"
#include "mmc_core.h"
#include "sdhci.h"

#define MCI_PARENT          "mci"
#define MCI_STATS_PROC   "mci_info"

#define MAX_CARD_TYPE   4
#define MAX_SPEED_MODE  5

static struct proc_dir_entry *g_procMciDir = NULL;

static char *ProcGetCardType(unsigned int type)
{
    static char *cardTypeStr[MAX_CARD_TYPE + 1] = {
        "unknown",
        "MMC card",
        "SD card",
        "SDIO card",
        "SD combo (IO+mem) card"
    };

    if (MAX_CARD_TYPE < type || type == 0) {
        return cardTypeStr[0];
    }
    else {
        return cardTypeStr[type];
    }
}

static unsigned int AnalyzeClockScale(unsigned int mmcClock,
                                                     unsigned int *clockVal)
{
    unsigned int scale = 0;
    unsigned int tmp = mmcClock;

    while (1) {
        tmp = tmp / 1000;
        if (0 < tmp) {
            *clockVal = tmp;
            scale++;
        } else {
            break;
        }
    }
    return scale;
}

static int ProcStatsUHSPrint(struct mmc_card  *card, struct seq_file *s)
{
    int status;
    const char *uhsBusSpeedMode = "";
    static const char *const speedsOfUHS[] = {
        "SDR12 ", "SDR25 ", "SDR50 ", "SDR104 ", "DDR50 "
    };

    if (is_card_uhs(card) && (card->bus_speed_mode <  ARRAY_SIZE(speedsOfUHS))) {
        uhsBusSpeedMode = speedsOfUHS[card->bus_speed_mode];
    }

    status = seq_printf(s, "\tMode: %s%s%s%s\n",
                       is_card_uhs(card) ? "UHS" :
                       (is_card_highspeed(card) ? "HS " : ""),
                       (is_card_hs200(card) ? "HS200 " : ""),
                       is_card_ddr_mode(card) ? "DDR" : "",  uhsBusSpeedMode);
    if (status != 0) {
        return -ENOMEM;
    } else {
        return 0;
    }
}

static int ProcStatsSpeedPrint(struct mmc_card  *card, struct seq_file *s)
{
    int status;
    unsigned int speedClass, gradespeedUHS;

    speedClass = card->card_reg.ssr.speed_class;
    gradespeedUHS = card->card_reg.ssr.uhs_speed_grade;
    status = seq_printf(s, "\tSpeed Class: Class %s\n",
                       (0x00 == speedClass) ? "0" :
                       (0x01 == speedClass) ? "2" :
                       (0x02 == speedClass) ? "4" :
                       (0x03 == speedClass) ? "6" :
                       (0x04 == speedClass) ? "10" :
                       "Reserved");
    if (status != 0) {
        return -ENOMEM;
    }

    status = seq_printf(s, "\tUhs Speed Grade: %s\n",
                      (0x00 == gradespeedUHS) ? "Less than 10MB/sec(0h)" :
                      (0x01 == gradespeedUHS) ? "10MB/sec and above(1h)" :
                       "Reserved");
    if (status != 0) {
        return -ENOMEM;
    } else {
        return 0;
    }
}

static int ProcStatsClkPrint(unsigned int mmcClock, struct seq_file *s)
{
    int status;
    static char *clockUnit[4] = {
        "Hz",
        "KHz",
        "MHz",
        "GHz"
    };
    unsigned int clockScale;
    unsigned int clockValue = 0;

    clockScale = AnalyzeClockScale(mmcClock, &clockValue);
    status = seq_printf(s, "\tHost work clock: %d%s\n",
                       clockValue, clockUnit[clockScale]);
    if (status != 0) {
        return -ENOMEM;
    }

    status = seq_printf(s, "\tCard support clock: %d%s\n",
                       clockValue, clockUnit[clockScale]);
    if (status != 0) {
        return -ENOMEM;
    }

    status = seq_printf(s, "\tCard work clock: %d%s\n",
                       clockValue, clockUnit[clockScale]);
    if (status != 0) {
        return -ENOMEM;
    } else {
        return 0;
    }
}

static int ProcStatsCIDPrint(struct mmc_card  *card, struct seq_file *s)
{
    int status;

    status = seq_printf(s, "\tCard cid: %x%04x%x%x%x%x%x%x%x%08x%03x%d%x\n",
                       card->card_reg.cid.mid, card->card_reg.cid.oid, card->card_reg.cid.pnm[0],
                       card->card_reg.cid.pnm[1], card->card_reg.cid.pnm[2], card->card_reg.cid.pnm[3],
                       card->card_reg.cid.pnm[4], card->card_reg.cid.hwrev, card->card_reg.cid.fwrev,
                       card->card_reg.cid.serial, card->card_reg.cid.year - 2000, card->card_reg.cid.month,
                       card->card_reg.cid.year - 2000);
    if (status != 0) {
        return -ENOMEM;
    } else {
        return 0;
    }
}

static int ProcStatsSeqPrint(struct seq_file *s)
{
    int status;
    unsigned int hostIdx;
    const char *type = NULL;
    struct mmc_host *mmc = NULL;
    struct mmc_card  *card = NULL;
    struct sdhc_host *host = NULL;

    for (hostIdx = 0; hostIdx < MAX_MMC_NUM; hostIdx++) {
        mmc = get_mmc_host(hostIdx);
        if (mmc == NULL || mmc->priv == NULL) {
            status = seq_printf(s, "\nMCI%d: invalid\n", hostIdx);
            if (status != 0) {
                return -ENOMEM;
            }
            continue;
        } else {
            status = seq_printf(s, "\nMCI%d", hostIdx);
            if (status != 0) {
                return -ENOMEM;
            }
        }
        card = mmc->card_cur;
        host = mmc->priv;
        if (card == NULL) {
            status = seq_printf(s, ": unplugged_disconnected\n");
            if (status != 0) {
                return -ENOMEM;
            }
        } else {
            status = seq_printf(s, ": plugged");
            if (status != 0) {
                return -ENOMEM;
            }
            if (!is_card_present(card)) {
                status = seq_printf(s, "_disconnected\n");
            } else {
                status = seq_printf(s, "_connected\n\tType: %s",
                                                ProcGetCardType(get_card_type(card)));
                if (status != 0) {
                    return -ENOMEM;
                }
                if (is_card_blkaddr(card)) {
                    if (is_card_ext_capacity(card)) {
                        type = "SDXC";
                    } else {
                        type = "SDHC";
                    }
                    status = seq_printf(s, "(%s)\n", type);
                    if (status != 0) {
                        return -ENOMEM;
                    }
                }
                status = ProcStatsUHSPrint(card, s);
                if (status != 0) {
                    return status;
                }
                status = ProcStatsSpeedPrint(card, s);
                if (status != 0) {
                    return status;
                }
                status = ProcStatsClkPrint(host->clock, s);
                if (status != 0) {
                    return status;
                }
                status = seq_printf(s, "\tCard error count: 0\n");
                if (status != 0) {
                    return -ENOMEM;
                }
                status = ProcStatsCIDPrint(card, s);
                if (status != 0) {
                    return -ENOMEM;
                }
            }
        }
    }
    return 0;
}

static int ProcMciShow(struct seq_file *m, void *v)
{
    return ProcStatsSeqPrint(m);
}

static int ProcMciOpen(struct inode *inode_ptr, struct proc_file *pf)
{
    return single_open(pf, ProcMciShow, NULL);
}

static const struct proc_file_operations g_mciProcFops = {
    .open       = ProcMciOpen,
    .read       = seq_read,
    .llseek     = seq_lseek,
    .release    = single_release,
    .write      = NULL, /*lint !e64*/
};

int ProcMciInit(void)
{
    struct proc_dir_entry *pHandle = NULL;

    g_procMciDir = proc_mkdir(MCI_PARENT, NULL);
    if (g_procMciDir == NULL) {
        PRINT_ERR("create directory error!\n");
        return -1;
    }

    pHandle = create_proc_entry(MCI_STATS_PROC, 0, (struct proc_dir_entry *)g_procMciDir);
    if (pHandle == NULL) {
        PRINT_ERR("create mount pointer error!\n");
        return -1;
    }

    pHandle->proc_fops = &g_mciProcFops;
    return 0;
}
